Adding User Authentication to a Ruby on the Rails Application

Adding User authentication to a Ruby on the rails app. This follows the method discussed in “Agile Web Development with Rails”. Some of the example code is copied from there.

0. Create user database:

rails generate scaffold User name:string password_digest:string
rake db:migrate

1. Edit app/models/user.rb such that it reads:

class User < ActiveRecord::Base
  validates :name, presence: true, uniqueness: true
  has_secure_password # This makes the password a digest and forces you to enter it twice.
end

2. add bcrypt to the gemfile, edit Gemfile:

gem 'bcrypt-ruby'

You may need to run “bundle install”.

3. Edit the user edit form to add password confirmation:

app/views/users/_form.html.erb so that it reads as follows:

<%= form_for(@user) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <fieldset>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :password, 'Password' %><br />
    <%= f.password_field :password %>
  </div>

  <div>
    <%= f.label :password_confirmation, 'Confirm' %>:
    <%= f.password_field :password_confirmation %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>

  </fieldset>
<% end %>

4. Create session controller…

rails generate controller Sessions new create destroy

Edit: app/controllers/sessions_controller.rb

Should look like this following edits:

class SessionsController < ApplicationController
  skip_before_filter :authorize

  def new
  end

  def create
    user = User.find_by_name(params[:name])
    if user and user.authenticate(params[:password])
      session[:user_id] = user.id
      #redirect_to admin_url # use this to redirect the user somewhere after login
    else
      redirect_to login_url, alert: "Invalid user/password combination"
    end
  end

  def destroy
    session[:user_id] = nil
  end

end

You should open localhost:3000/users at this point and create a user. We’ll be adding authentication next and you will need at least one user to be able to login and create more.

5. Create the login page.

Edit app/views/sessions/new.html.erb to read:

<div class="depot_form" >
<% if flash[:alert] %>
<p id="notice" ><%= flash[:alert] %></p>
<% end %>

<%= form_tag do %>
<fieldset>
<legend>Please Log In</legend>

<div>
<label for="name" >Name:</label>
<%= text_field_tag :name, params[:name] %>
</div>

<div>
<label for="password" >Password:</label>
<%= password_field_tag :password, params[:password] %>
</div>

<div>
<%= submit_tag "Login" %>
</div>
</fieldset>
<% end %>
</div>

Edit the route controller:

config/routes.rb remove:

  get "sessions/new"

  get "sessions/create"

  get "sessions/destroy"

add:

  controller :sessions do
    get 'login' => :new
    post 'login' => :create
    delete 'logout' => :destroy
  end

6. Now we need to add a “filter” to provide for authenication:

edit app/controllers/application_controller.rb

It should read as follows:

class ApplicationController < ActionController::Base
  protect_from_forgery

  before_filter :authorize

  # ...

  protected

  def authorize
    unless User.find_by_id(session[:user_id])
      redirect_to login_url, notice: "Please log in"
    end
  end
end

7. You’ll need to skip authorisation for all controllers that don’t need a login.

The following with skip authorisation for all methods in a controller add the following after the beginning of the class definiton:

skip_before_filter :authorize

For particular methods:

skip_before_filter :authorize, only: [:create, :update, :destroy]

8. Logoff code:

Add the following on pages where you want a logoff button:

<% if session[:user_id] %>
<%= button_to 'Logout', logout_path, method: :delete %>
<% end %>
</div>
<div id="main" >
<%= yield %>